// ADOBE SYSTEMS INCORPORATED
//  Copyright 2007 Adobe Systems Incorporated
//  All Rights Reserved.
// 
// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the 
// terms of the Adobe license agreement accompanying it.  If you have received this file from a 
// source other than Adobe, then your use, modification, or distribution of it requires the prior 
// written permission of Adobe.

// --------------------------------------------------------------------------
// AIRAppInstaller( version, url, count, respObj )
//
// You can use this class by
//  appInst = new AIRAppInstaller( version, url, count, respObj );
//  appInst.start();
//
// There are no other methods to call. If AIR
// is not installed, it will download it. If it is not
// running, it will launch it. Once running, it will
// use a LocalConnection to send() the installation
// request to AIR.
//
// The url must be absolute ... see function resolveUrl() for relative urls.
//
// AIR will connect to its LocalConnect endpoint upon launching.
// If we launch the application, we do not know how long this will take,
// so we will re-try the send() once per second for up to count
// seconds if we launched the application ... if it was already running,
// we will try only once.
//
// Note that respObj.onStatus() may be invoked //synchronously// by start(), 
// so be sure your code can handle this.


// Resolves a relative url against _root._url.
//
// If you are playing
//      'http://www.domain.com/folder/subfolder/movie.swf'
//
// then resolveUrl('service.swf') returns
//      'http://www.domain.com/folder/subfolder/service.swf'
//
// and resolveUrl('../service.swf') returns
//      'http://www.domain.com/folder/service.swf'
//
// and resolveUrl('../../service.swf') returns
//      'http://www.domain.com/service.swf'
//
// and resolveUrl('../../../service.swf') returns
//      'http://service.swf'
//
// (aka: garbage. Use it in a way you know will work)

function resolveUrl(url)
{
    // start with 'http://www.domain.com/folder/subfolder/movie.swf'
    var base = _root._url;

    // strip last component 'http://www.domain.com/folder/subfolder'
    base = base.slice(0, base.lastIndexOf('/'));

    // does url start with "../" (../service.swf)
    while (url.slice(0, 3) == "../")
    {
        // strip last component, 'again http://www.domain.com/folder'
        base = base.slice(0, base.lastIndexOf('/'));

        // strip leading "../" (service.swf)
        url = url.slice(3);
    }

    // return url ('http://www.domain.com/folder' + '/' + 'service.swf'
    return base + '/' + url;
}

//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
// fcisStatusConnection is a subclass of LocalConnection.
//
//  {level:"status", code:"InstallProduct.Started"}
//  {level:"status", code:"InstallProduct.Success"}
//  {level:"error",  code:"InstallProduct.Failed"}
//  {level:"error",  code:"InstallProduct.Cancelled"}
//
//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

function fcisStatusConnection(  respObj, connectionString )
{
    // get rid of the prefix on the url
    this.connectionString =  connectionString;
    this.respObj = respObj;

    this.connect( this.connectionString );
}

fcisStatusConnection.prototype = new LocalConnection();

fcisStatusConnection.prototype.allowDomain = function(domain)
{
    if (domain == "adobe.com")
        return true;
}

fcisStatusConnection.prototype.onReceive = function(msg)
{
    var level = ( msg == "InstallProduct.Started" || msg == "InstallProduct.Success" ) ? "status" : "error";

    if ( msg == "InstallProduct.Success" ||  msg == "InstallProduct.Failed"  ||
        msg == "InstallProduct.Cancelled" )
        this.close();

    this.respObj.lcStatus( { level : level, code : msg } );
}

//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
// fcisLocalConnection is a subclass of LocalConnection.
//
// AIRAppInstaller will create an instance with itself as the respObj.
//
// It will only call doInstall(count), which tries to send() an
// install message to AIR. If count is non-zero,
// and the message fails, then fcisLocalConnection will resend
// the message once per second for up to count seconds.
//
// respObj.onStatus() will be called with :
//  {level:"status", code:"InstallProduct.LocalConnection.SendPending"}
//  {level:"status", code:"InstallProduct.LocalConnection.SendSuccess"}
//  {level:"error",  code:"InstallProduct.LocalConnection.SendFailed"}
//
// respectively each time fcisLocalConnection send()'s, when
// fcisLocalConnection is done because the send() was dispatched, and
// when fcisLocalConnection is done because we've failed for more than
// count seconds.
//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

function fcisLocalConnection(respObj, url, statusConnection)
{
    this.url        = url;
    this.respObj    = respObj;
    this.statusConnection = this.domain() + ":" + statusConnection;
}

fcisLocalConnection.prototype = new LocalConnection();

fcisLocalConnection.prototype.finish = function(theLevel, theCode)
{
    // Stop the interval (if it's started), and send the final status
    if (this.intervalID)
    {
        clearInterval(this.intervalID);
        delete this.intervalID;
    }

    this.respObj.lcStatus( {level : theLevel, code : theCode} );
}

fcisLocalConnection.prototype.onStatus = function(info)
{
    // onStatus() is called in response to a send().

    if (info.level == "status")
    {
        // The message was dispatched.
        this.finish("status", "InstallProduct.LocalConnection.SendSuccess");
    }
    else
    {
        // The message was dropped.
        // Assume AIR hasn't connected, but will

        if ((this.count > 0) && !this.intervalID)
        {
            // This is the first time we failed.
            //
            // Start a half second interval, and retry until count hits zero.
            this.intervalID = setInterval(fcisPollDoInstall, 500, this);
        }
        else if (this.count < 1)
        {
            // We've retried for as long as we were supposed to.
            this.finish("error", "InstallProduct.LocalConnection.SendFailed");
        }
    }
};

fcisLocalConnection.prototype.doInstall = function(count)
{
    this.count = count; // number of seconds to retry, if the first send() fails.

    this.send("runtime#:air", "installFromBadge", this.url);
    this.respObj.lcStatus( {level : "status", code : "InstallProduct.LocalConnection.SendPending"} );
};


fcisPollDoInstall = function(lc)
{
    lc.doInstall(lc.count - 1);
}

// AIRAppInstaller is a subclass of Object.
//
// It will potentially download and/or launch AIR before
// sending an install request to a known LocalConnection made
// by AIR.
//
// The url passed to it must be absolute.
//
// If AIR had to be launched, the send may be tried for
// up to count seconds, as we wait for the other process to connect().
//
// respObj.onStatus() will be invoked with
//  {level:"status", code:"AIR.Download.Pending"}
//  {level:"error",  code:"AIR.Download.Failed"}
//  {level:"status", code:"AIR.Launch.Success"}
//  {level:"error",  code:"AIR.Launch.Failed"}
//
//  {level:"status", code:"InstallProduct.LocalConnection.SendPending"}
//  {level:"status", code:"InstallProduct.LocalConnection.SendSuccess"}
//  {level:"error",  code:"InstallProduct.LocalConnection.SendFailed"}
//
//  {level:"status", code:"InstallProduct.Started"}
//  {level:"status", code:"InstallProduct.Success"}
//  {level:"error",  code:"InstallProduct.Failed"}
//  {level:"error",  code:"InstallProduct.Cancelled"}
//
//
// AIRAppInstaller is done when it sends code "AIR.Success" or
// "AIR.Failed".
//
//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

function AIRAppInstaller(version, url, count, respObj)
{
    this.url	    = url;
    this.count      = count;
    this.respObj    = respObj;
    this.product    = new _global.System.Product("airappinstaller");

    // Starting with 1.0.M5, as long as the runtime is installed it will handle
    // download updates required by the app (i.e., 1.0.M6). However, the badge
    // was first released with M4 and M4 does *not* have this runtime update
    // capability. Therefore, if M4 is installed but the app is not an M4 app,
    // proceed as if the runtime was not installed. See [1576304].
    //
    // Note: Due to a bug in 1.0.M4, the Mac OS version of airappinstaller
    // from that release won't report a version number. That was fixed in
    // 1.0.M5, so it's ok to assume an empty version string means 1.0.M4.

    this.airInstalled = this.product.isInstalled();    
    var installedVersion = this.product.installedVersion();
    if( 
		version != "1.0.M4" &&
		( installedVersion == null || installedVersion.indexOf( "1.0.4" ) == 0 )
    ) {
		this.airInstalled = false;
	}
	
    version = version.toLowerCase().split(".").join("x");
    if( !this.airInstalled ) this.product = new _global.System.Product("airinstaller" + version);

    this.product.installer = this;
	this.product.onStatus = function(info) {
		// onStatus() is invoked when the download() stops.
	
		if (info.code == "Download.Complete")
		{
			// The download worked. Launch the application.
	
			if (this.launch())
				this.installer.fcStatus( {level : "status", code : "AIR.Launch.Success"} );
			else
				this.installer.fcStatus( {level : "error", code : "AIR.Launch.Failed"} );
		}
		else
		{
			this.installer.fcStatus( {level : "error", code : "AIR.Download.Failed"} );
		}
	}
}

AIRAppInstaller.prototype = new Object();

AIRAppInstaller.prototype.start = function()
{
    // in case start's been called previously, reset our state.
    delete this.lc;
    delete this.sc;

    if (this.url != undefined && this.url != "")
    {
		// FIXME: This will not work if the url contains more than 1 colon.
		//
        var connectionString  = this.url.slice(this.url.indexOf(':')+3, this.url.length) +  "/InstallResponse";

        this.sc         = new fcisStatusConnection( this, connectionString );
        this.lc         = new fcisLocalConnection( this, this.url, connectionString );
    }

	if (this.product.isInstalled())
    {
        if (this.product.launch())
            this.fcStatus( {level : "status", code : "AIR.Launch.Success"} );
        else
            this.fcStatus( {level : "error", code : "AIR.Launch.Failed"} );
    }
    else
    {
        if (this.product.download())
            this.fcStatus( {level : "status", code : "AIR.Download.Pending"} );
        else
            this.fcStatus( {level : "error", code : "AIR.Download.Failed"} );
    }
}

AIRAppInstaller.prototype.lcStatus = function(info)
{
    // Expect one of :
    //  {level:"status", code:"InstallProduct.LocalConnection.SendPending"}
    //  {level:"status", code:"InstallProduct.LocalConnection.SendSuccess"}
    //  {level:"error",  code:"InstallProduct.LocalConnection.SendFailed"}
    //
    //  {level:"status", code:"InstallProduct.Started"}
    //  {level:"status", code:"InstallProduct.Success"}
    //  {level:"error",  code:"InstallProduct.Failed"}
    //  {level:"error",  code:"InstallProduct.Cancelled"}
    //
    // forward the message
    this.respObj.onStatus(info);
}

AIRAppInstaller.prototype.fcStatus = function(info)
{
    // Expect one of :
    //  {level:"status", code:"AIR.Launch.Success"}
    //  {level:"status", code:"AIR.Download.Pending"}
    //  {level:"error",  code:"AIR.Download.Failed"}
    //  {level:"error",  code:"AIR.Launch.Failed"}

    // forward the message
    this.respObj.onStatus(info);

    if (this.url != undefined && this.url != "")
    {
        if (info.level == "status")
        {
            if (info.code == "AIR.Launch.Success")
            {
                // AIR was just launched. send the message, but
                // if it fails, retry once a second for up to 20 seconds.
                this.lc.doInstall(this.count);
            }
        }
        else
        {
            // generate our own status for terminal states.
            this.respObj.onStatus({level:"error",  code:"InstallProduct.Failed"});
        }
    }
}
